package com.cool.modules.finance.service.statistics.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONObject;
import com.cool.core.exception.CoolException;
import com.cool.core.util.CoolSecurityUtil;
import com.cool.modules.finance.constant.AccountType;
import com.cool.modules.finance.constant.OrderType;
import com.cool.modules.finance.dto.TabChartVo;
import com.cool.modules.finance.dto.statistics.*;
import com.cool.modules.finance.entity.account.FinanceAccountEntity;
import com.cool.modules.finance.entity.account.FinanceTrialBalanceEntity;
import com.cool.modules.finance.entity.category.FinanceCategoryEntity;
import com.cool.modules.finance.entity.coin.FinanceCoinEntity;
import com.cool.modules.finance.entity.order.FinanceOrderEntity;
import com.cool.modules.finance.service.account.FinanceAccountService;
import com.cool.modules.finance.service.account.FinanceTrialBalanceService;
import com.cool.modules.finance.service.category.FinanceCategoryService;
import com.cool.modules.finance.service.coin.FinanceCoinService;
import com.cool.modules.finance.service.family.FinanceUserFamilyService;
import com.cool.modules.finance.service.order.FinanceOrderService;
import com.cool.modules.finance.service.statistics.FinanceStatisticsBase;
import com.cool.modules.finance.service.statistics.FinanceStatisticsService;
import com.mybatisflex.core.query.QueryWrapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 小金库统计 service
 */
@Service
@RequiredArgsConstructor
public class FinanceStatisticsServiceImpl extends FinanceStatisticsBase implements FinanceStatisticsService {

    final FinanceOrderService financeOrderService;

    final FinanceAccountService accountService;

    final FinanceCategoryService categoryService;

    final FinanceTrialBalanceService trialBalanceService;

    final FinanceCoinService coinService;

    @Override
    public PayChartVo consumerInformationStatistics(JSONObject requestParams) {
        FinanceCoinEntity coinCode = getCoinCode(requestParams);
        Long familyId = getFamilyId();
        // 获取当前家庭当月的消费信息
        List<FinanceOrderEntity> list = financeOrderService.list(new QueryWrapper().eq(FinanceOrderEntity::getFamilyId, familyId).eq(FinanceOrderEntity::getOrderType, OrderType.EXPEND).eq(FinanceOrderEntity::getCoinId, coinCode.getId()).between(FinanceOrderEntity::getOrderDate, getFirstDayOfCurrentMonth(), getFirstDayOfNextMonth()));
        //获取月总消费数
        BigDecimal monthlyTotalConsumption = list.stream().map(FinanceOrderEntity::getOrderMoney).reduce(BigDecimal.ZERO, BigDecimal::add);
        //获取月日常消费数
        BigDecimal monthlyDailyConsumption = list.stream().filter(FinanceOrderEntity::getIsDaily).map(FinanceOrderEntity::getOrderMoney).reduce(BigDecimal.ZERO, BigDecimal::add);
        //获取月非日常消费数
        BigDecimal monthlyNonDailyConsumption = list.stream().filter(x -> !x.getIsDaily()).map(FinanceOrderEntity::getOrderMoney).reduce(BigDecimal.ZERO, BigDecimal::add);
        //获取月平均日常消费数
        BigDecimal monthlyAverageDailyConsumption = monthlyDailyConsumption.divide(new BigDecimal(getDaysInCurrentMonth()), 2, RoundingMode.HALF_UP);
        return new PayChartVo(monthlyTotalConsumption, monthlyDailyConsumption, monthlyNonDailyConsumption, monthlyAverageDailyConsumption, coinCode.getSymbol());
    }

    @Override
    public CountViewVo paymentCountStatistics(JSONObject requestParams) {
        FinanceCoinEntity coinCode = getCoinCode(requestParams);
        CountViewVo countViewVo = new CountViewVo();
        Long familyId = getFamilyId();
        // 获取当前家庭当月的消费信息
        List<FinanceOrderEntity> list = financeOrderService.list(new QueryWrapper().eq(FinanceOrderEntity::getFamilyId, familyId).eq(FinanceOrderEntity::getOrderType, OrderType.EXPEND).eq(FinanceOrderEntity::getCoinId, coinCode.getId()).between(FinanceOrderEntity::getOrderDate, getFirstDayOfCurrentMonth(), getFirstDayOfNextMonth()));
        countViewVo.setMonthlyPaymentAmount(list.size());
        Map<Date, List<FinanceOrderEntity>> collect = list.stream().collect(Collectors.groupingBy(FinanceOrderEntity::getOrderDate));

        collect.keySet().stream().map(o -> collect.get(o).size()).toList();

        List<String> day = new ArrayList<>();
        List<Integer> count = new ArrayList<>();
        for (int i = 1; i <= getDaysInCurrentMonth(); i++) {
            String dayStr = i <= 9 ? "0" + i : i + "";
            day.add(dayStr);
            Optional<Date> rs = collect.keySet().stream().filter(o -> new SimpleDateFormat("dd").format(o).equals(dayStr)).findFirst();
            count.add(rs.map(date -> collect.get(date).size()).orElse(0));
        }
        countViewVo.setDay(day);
        countViewVo.setCount(count);
        count.stream().max(Integer::compareTo).ifPresent(max -> {
            int i = count.indexOf(max);
            countViewVo.setTheHighestFrequencyOfConsumption(max);
            String month = getCurrentMonth() <= 9 ? "0" + getCurrentMonth() + "月" : getCurrentMonth() + "月";
            countViewVo.setHighestConsumptionDate(month + day.get(i) + "日");
        });
        return countViewVo;
    }

    @Override
    public CountPaidVo paymentAmountStatistics(JSONObject requestParams) {
        FinanceCoinEntity coinCode = getCoinCode(requestParams);
        CountPaidVo countPaidVo = new CountPaidVo();
        countPaidVo.setCoinSymbol(coinCode.getSymbol());
        Long familyId = getFamilyId();
        List<FinanceOrderEntity> list = financeOrderService.list(new QueryWrapper().eq(FinanceOrderEntity::getFamilyId, familyId).eq(FinanceOrderEntity::getOrderType, OrderType.EXPEND).eq(FinanceOrderEntity::getCoinId, coinCode.getId()).between(FinanceOrderEntity::getOrderDate, getFirstDayOfCurrentMonth(), getFirstDayOfNextMonth()));
        Map<Date, List<FinanceOrderEntity>> collect = list.stream().collect(Collectors.groupingBy(FinanceOrderEntity::getOrderDate));
        List<String> day = new ArrayList<>();
        List<BigDecimal> amount = new ArrayList<>();
        for (int i = 1; i <= getDaysInCurrentMonth(); i++) {
            String dayStr = i <= 9 ? "0" + i : i + "";
            day.add(dayStr);
            Optional<Date> rs = collect.keySet().stream().filter(o -> new SimpleDateFormat("dd").format(o).equals(dayStr)).findFirst();
            amount.add(rs.map(date -> collect.get(date).stream().map(FinanceOrderEntity::getOrderMoney).reduce(BigDecimal.ZERO, BigDecimal::add)).orElse(BigDecimal.ZERO));
        }
        countPaidVo.setDay(day);
        countPaidVo.setAmount(amount);
        list.stream().max(Comparator.comparing(FinanceOrderEntity::getOrderMoney)).ifPresent(max -> {
            countPaidVo.setMonthlyHighestConsumptionDay(new SimpleDateFormat("MM月dd日").format(max.getOrderDate()));
            countPaidVo.setMonthlyHighestConsumptionAmount(max.getOrderMoney());
        });
        return countPaidVo;
    }

    @Override
    public AccountBalanceVo accountBalanceStatistics(JSONObject requestParams) {
        FinanceCoinEntity coinCode = getCoinCode(requestParams);
        List<Long> familyUserIds = getFamilyUserIds();
        // 获取家庭所有成员的未注销账户
        List<FinanceAccountEntity> list = accountService.list(new QueryWrapper().in(FinanceAccountEntity::getOwnedAccount, familyUserIds).eq(FinanceAccountEntity::getIsCancel, false).eq(FinanceAccountEntity::getCoinId, coinCode.getId()));
        BigDecimal conductFinancialTransactions = list.stream().filter(x -> x.getAccountType().equals(AccountType.FINANCE.getType())).map(FinanceAccountEntity::getAccountBalance).reduce(BigDecimal.ZERO, BigDecimal::add);
        // 获取家庭所有成员的非借记账户余额
        BigDecimal notDebit = list.stream().filter(x -> !x.getAccountType().equals(AccountType.DEBIT.getType())).map(FinanceAccountEntity::getAccountBalance).reduce(BigDecimal.ZERO, BigDecimal::add);
        // 获取家庭所有成员的借记账户欠款
        BigDecimal debit = list.stream().filter(x -> x.getAccountType().equals(AccountType.DEBIT.getType())).map(x -> x.getAccountMoney().subtract(x.getAccountMoney()).abs()).reduce(BigDecimal.ZERO, BigDecimal::add);
        return new AccountBalanceVo(notDebit.subtract(debit), conductFinancialTransactions, coinCode.getSymbol());
    }

    @Override
    public TabChartVo tabChartStatistics(JSONObject requestParams) {
        FinanceCoinEntity coinCode = getCoinCode(requestParams);
        TabChartVo.TabChart income = new TabChartVo.TabChart();
        TabChartVo.TabChart outcome = new TabChartVo.TabChart();
        TabChartVo tabChartVo = new TabChartVo(income, outcome);
        List<Calendar> dates = new ArrayList<>();
        Calendar now = Calendar.getInstance();
        now.setTime(getFirstDayOfCurrentMonth());
        for (int i = 0; i < 12; i++) {
            Calendar temp = Calendar.getInstance();
            temp.setTime(now.getTime());
            dates.add(temp);
            now.add(Calendar.MONTH, -1);
        }
        List<Integer> months = dates.stream().map(x -> x.get(Calendar.MONTH) + 1).collect(Collectors.toList());
        Collections.reverse(months);
        income.setCategory(months);
        outcome.setCategory(months);
        Long familyId = getFamilyId();
        //本月最后一天的23点59分59秒
        Calendar end = Calendar.getInstance();
        end.setTime(getFirstDayOfNextMonth());
        end.add(Calendar.SECOND, -1);
        //下月1号往前一年
        Calendar start = Calendar.getInstance();
        start.setTime(getFirstDayOfNextMonth());
        start.add(Calendar.YEAR, -1);
        //最近一年的所有收支数据
        List<FinanceOrderEntity> list = financeOrderService.list(new QueryWrapper().eq(FinanceOrderEntity::getFamilyId, familyId).eq(FinanceOrderEntity::getCoinId,coinCode.getId()).between(FinanceOrderEntity::getOrderDate, start.getTime(), end.getTime()));
        //收入与支出区分
        Map<OrderType, List<FinanceOrderEntity>> collect = list.stream().collect(Collectors.groupingBy(FinanceOrderEntity::getOrderType));
        income.setValue(getEveryMonthMoney(OrderType.INCOME, dates, collect));
        outcome.setValue(getEveryMonthMoney(OrderType.EXPEND, dates, collect));
        return tabChartVo;
    }

    @Override
    public List<ExpendRankVo> expendRank(JSONObject requestParams) {
        FinanceCoinEntity coinCode = getCoinCode(requestParams);
        Long familyId = getFamilyId();
        Date start = new Date();
        Date end = new Date();
        String type = requestParams.containsKey("type") ? requestParams.getStr("type") : "month";
        if (type.equals("day")){
            Calendar temp = Calendar.getInstance();
            temp.set(Calendar.HOUR_OF_DAY, 0);
            temp.set(Calendar.MINUTE, 0);
            temp.set(Calendar.SECOND, 0);
            temp.set(Calendar.MILLISECOND, 0);
            start = temp.getTime();
            temp.add(Calendar.DAY_OF_MONTH,1);
            end = temp.getTime();
        } else if (type.equals("week")) {
            Calendar temp = Calendar.getInstance();
            temp.set(Calendar.HOUR_OF_DAY, 0);
            temp.set(Calendar.MINUTE, 0);
            temp.set(Calendar.SECOND, 0);
            temp.set(Calendar.MILLISECOND, 0);
            // 获取当前周的第一天（星期一）
            temp.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
            start = temp.getTime();
            // 获取当前周的最后一天（星期日）
            temp.add(Calendar.DAY_OF_WEEK, 6);
            end = temp.getTime();
        } else if (type.equals("month")) {
            start = getFirstDayOfCurrentMonth();
            end = getFirstDayOfNextMonth();
        } else if (type.equals("year")) {
            // 获取当前年的第一天
            Calendar temp = Calendar.getInstance();
            temp.set(Calendar.HOUR_OF_DAY, 0);
            temp.set(Calendar.MINUTE, 0);
            temp.set(Calendar.SECOND, 0);
            temp.set(Calendar.MILLISECOND, 0);
            temp.set(Calendar.DAY_OF_YEAR, 1);
            start = temp.getTime();
            // 获取当前年的最后一天
            temp.add(Calendar.YEAR, 1);
            temp.add(Calendar.DAY_OF_YEAR, -1);
            end = temp.getTime();
        } else if (type.equals("custom")) {
            Date date = requestParams.getDate("date");
            Calendar temp = Calendar.getInstance();
            temp.setTime(date);
            temp.set(Calendar.HOUR_OF_DAY, 0);
            temp.set(Calendar.MINUTE, 0);
            temp.set(Calendar.SECOND, 0);
            temp.set(Calendar.MILLISECOND, 0);
            start = temp.getTime();
            temp.add(Calendar.DAY_OF_MONTH,1);
            end = temp.getTime();
        }
        return financeOrderService.list(new QueryWrapper().eq(FinanceOrderEntity::getFamilyId, familyId).eq(FinanceOrderEntity::getCoinId,coinCode.getId()).eq(FinanceOrderEntity::getOrderType,OrderType.EXPEND).between(FinanceOrderEntity::getOrderDate, start, end).limit(5).orderBy(FinanceOrderEntity::getOrderMoney,false)).stream().map(ExpendRankVo::new).toList();
    }

    @Override
    public ProfitSearchVo profitSearch(JSONObject requestParams) {
        FinanceCoinEntity coinCode = getCoinCode(requestParams);
        ProfitSearchVo profitSearchVo = new ProfitSearchVo();
        profitSearchVo.setCoinSymbol(coinCode.getSymbol());
        List<Long> userIds = getFamilyUserIds();
        //当前家庭所有成员的理财账户信息
        List<FinanceAccountEntity> list = accountService.list(new QueryWrapper().eq(FinanceAccountEntity::getAccountType,AccountType.FINANCE.getType()).eq(FinanceAccountEntity::getCoinId,coinCode.getId()).in(FinanceAccountEntity::getOwnedAccount, userIds));
        //获取家庭理财账户的总理财数量
        profitSearchVo.setTotalNumberOfFinancialProducts(list.size());
        //当前家庭所有理财账户中持有理财账户数量
        profitSearchVo.setCurrentlyHoldingWealthManagement(list.stream().filter(x -> !x.getIsRedeem()).count());
        //当前家庭所有理财账户中赎回理财账户数量
        profitSearchVo.setRedemptionOfWealthManagement(list.stream().filter(FinanceAccountEntity::getIsRedeem).count());
        //已赎回的理财账户id集合
        List<Long> redeemAccountIds = list.stream().filter(FinanceAccountEntity::getIsRedeem).map(FinanceAccountEntity::getId).toList();
        //已经赎回的理财账户的利息
        List<FinanceTrialBalanceEntity> redeemList = CollectionUtil.isEmpty(redeemAccountIds) ?Collections.emptyList():trialBalanceService.list(new QueryWrapper().in(FinanceTrialBalanceEntity::getAccountId, redeemAccountIds).eq(FinanceTrialBalanceEntity::getIsFinal,true).orderBy(FinanceTrialBalanceEntity::getTrialDate,true));
        BigDecimal reduce = redeemList.stream().map(FinanceTrialBalanceEntity::getInterest).reduce(BigDecimal.ZERO, BigDecimal::add);
        profitSearchVo.setTotalAmountOfFinancialInterest(reduce);
        //设置理财账户的赎回趋势
        profitSearchVo.setRedemptionInterestTrend(redeemList.stream().map(FinanceTrialBalanceEntity::getInterest).toList());
        //设置理财账户排名
        List<FinanceTrialBalanceEntity> tempList = trialBalanceService.list(new QueryWrapper().eq(FinanceTrialBalanceEntity::getIsFinal,true).in(FinanceTrialBalanceEntity::getAccountId, list.stream().map(FinanceAccountEntity::getId).toList()).orderBy(FinanceTrialBalanceEntity::getRate, false).limit(5));
        List<ProfitSearchVo.redemptionAccount> redemptionAccounts = tempList.stream().map(x -> {
            ProfitSearchVo.redemptionAccount redemptionAccount = new ProfitSearchVo.redemptionAccount();
            list.stream().filter(a -> a.getId().equals(x.getAccountId())).findFirst().ifPresent(a -> {
                redemptionAccount.setAccountName(a.getAccountName());
            });
            redemptionAccount.setCapital(x.getCapital());
            redemptionAccount.setInterest(x.getInterest());
            redemptionAccount.setRate(x.getRate());
            redemptionAccount.setMedianRate(x.getMedianRate());
            redemptionAccount.setDays(x.getDays());
            return redemptionAccount;
        }).toList();
        profitSearchVo.setRedemptionAccount(redemptionAccounts);
        return profitSearchVo;
    }

    @Override
    public CategoryRatioVo categoryRatio(JSONObject requestParams) {
        FinanceCoinEntity coinCode = getCoinCode(requestParams);
        CategoryRatioVo categoryRatioVo = new CategoryRatioVo();
        categoryRatioVo.setCoinSymbol(coinCode.getSymbol());
        Long familyId = getFamilyId();
        // 获取当前家庭当月的消费信息
        List<FinanceOrderEntity> list = financeOrderService.list(new QueryWrapper().eq(FinanceOrderEntity::getFamilyId, familyId).eq(FinanceOrderEntity::getCoinId, coinCode.getId()).eq(FinanceOrderEntity::getOrderType, OrderType.EXPEND).between(FinanceOrderEntity::getOrderDate, getFirstDayOfCurrentMonth(), getFirstDayOfNextMonth()));
        Map<Long, List<FinanceOrderEntity>> collect = list.stream().collect(Collectors.groupingBy(FinanceOrderEntity::getCategoryId));
        List<FinanceCategoryEntity> categorys = categoryService.list(new QueryWrapper().in(FinanceCategoryEntity::getId, collect.keySet()));
        categoryRatioVo.setCategory(categorys.stream().map(FinanceCategoryEntity::getCategoryName).toList());
        categoryRatioVo.setRatio(collect.keySet().stream().map(x->{
            Optional<FinanceCategoryEntity> first = categorys.stream().filter(o -> o.getId().equals(x)).findFirst();
            if(first.isPresent()){
                FinanceCategoryEntity c = first.get();
                return new CategoryRatioVo.CategoryRatio(c.getCategoryName(), collect.get(x).stream().map(FinanceOrderEntity::getOrderMoney).reduce(BigDecimal.ZERO, BigDecimal::add));
            }
            return null;
        }).filter(Objects::nonNull).toList());
        return categoryRatioVo;
    }

    /**
     * 获取每月收入支出金额
     *
     * @param orderType
     * @param dates
     * @param collect
     * @return
     */
    private List<BigDecimal> getEveryMonthMoney(OrderType orderType, List<Calendar> dates, Map<OrderType, List<FinanceOrderEntity>> collect) {
        if (collect.get(orderType) == null) {
            return dates.stream().map(date -> BigDecimal.ZERO).toList();
        }
        List<BigDecimal> tempValue = dates.stream().map(date ->
                collect.get(orderType).stream().filter(order -> {
                    Calendar instance = Calendar.getInstance();
                    instance.setTime(date.getTime());
                    Date tempStart = instance.getTime();
                    instance.add(Calendar.MONTH, 1);
                    Date tempEnd = instance.getTime();
                    return order.getOrderDate().getTime()>=tempStart.getTime() && order.getOrderDate().getTime()<tempEnd.getTime();
                }).map(FinanceOrderEntity::getOrderMoney).reduce(BigDecimal.ZERO, BigDecimal::add)
        ).collect(Collectors.toList());
        Collections.reverse(tempValue);
        return tempValue;
    }

    /**
     * 获取币种
     * @param requestParams
     * @return
     */
    private FinanceCoinEntity getCoinCode(JSONObject requestParams) {
        String coinCode = requestParams.getStr("coinCode");
        if (coinCode == null) {
            throw new CoolException("当前菜单没有设置币种，请设置后再试");
        }
        FinanceCoinEntity one = coinService.getOne(new QueryWrapper().eq(FinanceCoinEntity::getCoinCode, coinCode));
        if (one == null) {
            throw new CoolException("当前币种不存在，请重新设置后再试");
        }
        return one;
    }
}
